using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace Qreed.Xna
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class CameraComponent : Microsoft.Xna.Framework.GameComponent, ICamera
    {
        #region View

        private bool _isViewDirty = true;

        private Vector3 _position = Vector3.Zero;
        /// <summary>
        /// Gets or sets the position.
        /// </summary>
        /// <value>The position.</value>
        public Vector3 Position
        {
            get { return _position; }
            set 
            { 
                _position = value;
                _isViewDirty = true;
            }
        }

        private Matrix _view = Matrix.Identity;
        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        public Matrix View
        {
            get 
            {
                if (_isViewDirty) UpdateView();
                return this._view; 
            }
        }

        /// <summary>
        /// Gets the forward.
        /// </summary>
        /// <value>The forward.</value>
        public Vector3 Forward
        {
            get 
            {
                if (_isViewDirty) UpdateView();
                return Matrix.Invert(this.View).Forward; 
            }
        }

        /// <summary>
        /// Gets the right.
        /// </summary>
        /// <value>The right.</value>
        public Vector3 Right
        {
            get 
            {
                if (_isViewDirty) UpdateView();
                return Matrix.Invert(this.View).Right; 
            }
        }

        /// <summary>
        /// Gets up.
        /// </summary>
        /// <value>Up.</value>
        public Vector3 Up
        {
            get 
            {
                if (_isViewDirty) UpdateView();
                return Matrix.Invert(this.View).Up; 
            }
        }

        private Vector3 _rotation = Vector3.Zero;
        /// <summary>
        /// Gets or sets the rotation.
        /// </summary>
        /// <value>The rotation.</value>
        public Vector3 Rotation
        {
            get { return _rotation; }
            set { _rotation = value; _isViewDirty = true; }
        }
			
        #endregion

        #region Projection

        private bool _isProjectionDirty = true;

        private Matrix _projection = Matrix.Identity;
        /// <summary>
        /// Gets the projection.
        /// </summary>
        /// <value>The projection.</value>
        public Matrix Projection
        {
            get { return this._projection; }
        }

        private Viewport _viewPort;
        /// <summary>
        /// Gets the view port.
        /// </summary>
        /// <value>The view port.</value>
        public Viewport ViewPort
        {
            get { return this._viewPort; }
        }

        private float _fieldOfView = MathHelper.ToRadians(50.0f);
        /// <summary>
        /// Gets or sets the field of view in radians.
        /// </summary>
        /// <value>The field of view.</value>
        public float FieldOfView
        {
            get { return this._fieldOfView; }
            set 
            { 
                this._fieldOfView = value;
                _isProjectionDirty = true;
            }
        }

        private float _nearPlaneDistance = 0.1f;
        /// <summary>
        /// Gets or sets the near plane distance.
        /// </summary>
        /// <value>The near plane distance.</value>
        public float NearPlaneDistance
        {
            get { return this._nearPlaneDistance; }
            set 
            { 
                this._nearPlaneDistance = value;
                _isProjectionDirty = true;
            }
        }

        private float _farPlaneDistance = 1000.0f;
        /// <summary>
        /// Gets or sets the far plane distance.
        /// </summary>
        /// <value>The far plane distance.</value>
        public float FarPlaneDistance
        {
            get { return this._farPlaneDistance; }
            set 
            { 
                this._farPlaneDistance = value;
                _isProjectionDirty = true;
            }
        }						

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="CameraComponent"/> class.
        /// </summary>
        /// <param name="game">Game that the game component should be attached to.</param>
        public CameraComponent(Game game)
            : base(game)
        {
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// Moves the camera by the specified offset.
        /// </summary>
        /// <param name="offset">The offset.</param>
        public void Move(Vector3 offset)
        {
            Matrix.Invert(ref _view, out _view);
            _view.Translation = offset;
            Matrix.Invert(ref _view, out _view);
        }

        /// <summary>
        /// Looks at.
        /// </summary>
        /// <param name="target">The target.</param>
        public void LookAt(Vector3 target)
        {
            _view = Matrix.CreateLookAt(Position, target, Vector3.Up);

            Vector3 forward = Matrix.Invert(_view).Forward;
            Vector3 floorProjection = new Vector3(forward.X, 0, forward.Z);
            float directionLength = floorProjection.Length();
            
            _rotation = new Vector3(-(float)Math.Atan2(forward.Y, directionLength),
                                    (float)Math.Atan2(forward.X, -forward.Z), 
                                    0.0f);

            _isViewDirty = false;
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (_isProjectionDirty) UpdateProjection();
            if (_isViewDirty) UpdateView();

            base.Update(gameTime);
        }

        /// <summary>
        /// Updates the view.
        /// </summary>
        internal void UpdateView()
        {
            _view = Matrix.Identity;
            _view *= Matrix.CreateTranslation(-Position);
            _view *= Matrix.CreateRotationZ(Rotation.Z);
            _view *= Matrix.CreateRotationY(Rotation.Y);
            _view *= Matrix.CreateRotationX(Rotation.X);

            _isViewDirty = false;    
        }

        /// <summary>
        /// Updates the projection.
        /// </summary>
        internal void UpdateProjection()
        {
            _viewPort = Game.GraphicsDevice.Viewport;
            _projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView, ViewPort.AspectRatio, NearPlaneDistance, FarPlaneDistance);
            
            _isProjectionDirty = false;
        }
    }
}